web编程

1. HTTP

HTTP(超文本传输协议)是一个无状态的,基于文本的协议。

HTTP 将状态码分为了 5 大类,1XX/2XX/3XX/4XX/5XX

1XX:情报状态码,又叫做信息状态码。服务器通过这类状态码告知客户端,自己已经收到了客户端发送的请求。几个常见的状态码如下:

  • 100 Continue:表示服务器到目前为止收到的内容都正常,客户端应该继续请求。如果已经完成,则忽略它。
  • 101 Switching Protocol:这个状态码是响应客户端的Upgrade首部发送的,并且指示服务器也正在切换的协议。如客户端请求切换协议,服务器将协议切换至 Websocket,就会发送该状态码给客户端,并且在Upgrade首部中填上 Websocket。

2XX:成功状态码。表示服务器已经收到了客户端的请求,并成功对请求进行了处理。几个常见的状态码如下:

  • 200 OK:这最常见的状态码了,表示请求成功。
  • 201 Created:请求已成功,并因此创建了一个新的资源。

3XX:重定向状态码。服务器收到了请求,但是为了完整地处理该请求,客户端还需要执行指定的动作。一般用于 URL 重定向。几个常见的状态码如下:

  • 300 Multiple Choice:被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址。用户或浏览器可自行选择一个地址进行重定向。
  • 302 Moved Permanently:被请求的资源已经永久移动到新的位置了。

4XX:客户端错误状态码。表示客户端发送的请求中有错误,如格式不对。常见的状态码如下:

  • 404 Not Found:最常见的状态码了,表示页面不存在。
  • 405 Method Not Allowed:请求的方法不允许。

5XX:服务器错误状态码。表示服务器由于某些原因无法正确处理请求。常见的状态码如下:

  • 500 Internal Server Error:服务器遇到了不知道如何处理的情况。
  • 501 Not Implemented:此请求方法不被服务器支持。

2. net/http

我们将使用 Go 语言提供的 net/http 包来实现一个简单的Go Web程序。该包的功能十分强大,使用起来也非常方便。

  1. 使用 HandleFunc 函数是http封装好的一个函数,可以直接使用,第一个参数是web请求路径,第二个参数是的 func(writer http.ResponseWriter, request *http.Request) 函数。
    再使用http.ListenAndServe(“:8080”,nil)语句,监听8080端口,运行程序后。
    其中 http.ResponseWriter是接口类型代表对客户端的响应体,而 http.Request是指针类型 代表客户端发送服务器的请求数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import(
"fmt"
"log"
"net/http"
)
func hello(w http.Responserite, r *http.Request) {
// fmt.Fprintf(w, "Hello World")
w.Write([]byte("一起学习Go Web编程吧"));
}
func main() {
http.Handlefunc("/index", hello)
if err := http.ListenAndServe(":8000", nil); err != nil {
log.Fatal(err)
}
}

处理器和处理器函数

Handler:处理器接口,定义在 net/http 包中。实现该接口的类型,其对象可以注册到多路复用器中;
Handle:注册处理器的方法;
HandleFunc:注册处理器函数的方法;
HandlerFunc:底层类型为func (w ResponseWriter, r *Request)的新类型,实现了Handler接口。它连接了处理器函数与处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World")
}
type greetingHandler struct {
Name string
}
func (h *greetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s", h.Name)
}
func main() {
mux := http.NewServeMux()
// 注册处理器函数
mux.HandleFunc("/hello", helloHandler)
// 注册处理器
mux.Handle("/greeting/golang", &greetingHandler{Name: "Golang"})
server := &http.Server {
Addr : ":8000",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}

3. 中间件

中间件:就是连接上下级不同功能的函数,通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。

go的 http 中间件很简单,只要实现一个函数签名为 func(http.Handler) http.Handler 的函数即可。http.Handler是一个接口,接口方法我们熟悉的为 ServeHTTP 。返回也是一个handler。因为go中的函数也可以当成变量传递或者或者返回,因此也可以在中间件函数中传递定义好的函数,只要这个函数是一个handler即可,即实现或者被handlerFunc包裹成为handler处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 中间件
func loggingHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
http.Handle("/", loggingHandler(http.HandlerFunc(hello)))
if err := http.ListenAndServe(":8000", nil); err != nil {
log.Fatal(err)
}
}

4. Go处理Web数据响应

5. 常用框架

目前 Go 社区已经有非常多关于 Web 开发的库或框架。大而全的有beegorevel。超高性能的有echofasthttpgin(目前 GitHub 星标最多)。还有不少专注于具体某个方面的,最多要属路由了,例如:mux/httprouter。

6. Gin

6.1 导入gin包

1
import "github.com/gin-gonic/gin"

6.2 创建路由

  • 使用 gin.Default() 方法会返回 gin.Engine 实例,表示默认路由引擎,这种方式会默认使用 LoggerRecovery 两个中间件。

    1
    router := gin.Default()
  • gin.New() 方法创建一个不包含任何中间件的默认路由。

1
router := gim.New()

6.3 定义处理HTTP的方法

Gin支持所有通用的HTTP请求方法:GET, POST, PUT, PATCH, OPTIONS, HEAD, DELETE

1
2
3
4
5
6
7
8
9
func hello(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"})
}
// GET
r.GET("/test", hello)
// POST
r.POST("/test", hello)

6.4 监听端口

定义好请求之后,使用 Run() 方法便可监听端口,开始接受HTTP请求, 如果Run()方法没有传入参数的话,则默认监听的端口是8080。

1
2
3
router.Run() // 默认8080端口
router.Run("9060") // 自定义9060端口

6.5 中间件

  • 返回值类型:gin.HandlerFunc
  • 返回值是:func(c *gin.Context){}
  • 返回值中: c.Next 之前代码会在api请求初始化时执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
func IPAuth() gin.HandlerFunc {
return func(c *gin.Context) {
ipList := []string{
"127.0.0.1",
}
flag := false
clientIp := c.ClientIP()
for _, value := range ipList {
if clientIp == value {
flag = true
break
}
}
if !flag {
c.String(http.StatusUnauthorized, "%s, not in ipList", clientIp)
return
}
c.Next()
}
}

6.6 优雅的关停服务器

内核在某些情况下发送信号,比如在进程往一个已经关闭的管道写数据时会产生SIGPIPE信号

在终端执行特定的组合键可以使系统发送特定的信号给此进程,完成一系列的动作:

  • ctrl + c: SIGINT 强制进程结束
  • ctrl + z: SIGTSTP 任务中断,进程挂起
  • ctrl + d: EOF

我们执行 ctrl + c 关闭gin服务端时,会强制进程结束,导致正在访问的用户等出现问题。

目的:

  • 不关闭现有连接(正在运行中的程序)。
  • 新的进程启动并替代旧进程。
  • 新的进程接管新的连接。
  • 连接要随时响应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不可以出现拒绝请求的情况。

1. 原始的关停服务器

1
开始 --> gin实例 --> gin.Run()阻塞 --> 结束

2. 优雅关闭服务器

1
2
3
4
5
os.Signal阻塞 ——————————————
^ |(设定超时5s
| |
开始 --> gin实例 --> server.ListenAndServe() --> 结束

ListenAndServe 是不阻塞的。监听关闭信号,如果获取到信号就把这个超时的上下文传入server的 shutdown() 里面,然后才正式退出,监听到信号到真正结束之前我们会首先会关闭这个时间段内重新进入的连接请求,且在超时时间内把之前已经接收到的请求执行完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main
import (
"context"
"gin_demo/router"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
router := router.InitRouter()
// 阻塞
// router.Run(":9060")
// 优雅关停
server := &http.Server{
Addr: ":9060",
Handler: router,
}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("shutdown server...")
// 创建10s的超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("server shutdown:", err)
}
log.Println("server exiting...")
}